package com.heima.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.common.constants.search.ArticleIndexConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.threadlocal.AppThreadLocalUtils;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.model.search.vo.SearchArticleVo;
import com.heima.model.user.pojos.ApUser;
import com.heima.search.service.ApUserSearchService;
import com.heima.search.service.ArticleSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @作者 itcast
 * @创建日期 2021/4/25 10:25
 **/
@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {
    @Autowired
    RestHighLevelClient restHighLevelClient;

    @Value("${file.oss.web-site}")
    String webSite;

    @Autowired
    ApUserSearchService apUserSearchService;

    @Override
    public ResponseResult search(UserSearchDto dto) {
        // 1. 检查参数  关键词必须有  分页 = 10     最小时间 = now
        if(StringUtils.isBlank(dto.getSearchWords())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"搜索关键词不能为空");
        }
        if(dto.getPageSize()==0){
            dto.setPageSize(10);
        }
        if(dto.getMinBehotTime() == null){
            dto.setMinBehotTime(new Date());
        }
        // 如果用户登录了  并且 fromIndex == 0 需要记录用户的搜索历史记录
        ApUser user = AppThreadLocalUtils.getUser();
        if(user != null && dto.getFromIndex() == 0){
            dto.setEntryId(user.getId());// 登录用户ID
            apUserSearchService.insert(dto);
        }
        // 2. 构建ES搜索请求 构建searchSourceBuilder 请求参数构建对象
        SearchRequest searchRequest = new SearchRequest(ArticleIndexConstants.ARTICLE_INDEX_NAME);
        SearchSourceBuilder builder = new SearchSourceBuilder();
        // 3. 构建搜索条件:
        // 3.1 创建布尔条件 must(filter) should must_not
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 3.2 创建分词查询 按照title字段查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("title",dto.getSearchWords()));
        // 3.3 创建范围查询 按照publishTime字段查询 小于最小时间
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime()));
        builder.query(boolQueryBuilder);
        // 3.4 设置排序条件 按照发布时间降序
        builder.sort("publishTime", SortOrder.DESC);
        // 3.5 设置分页条件 按照pageSize设置
        builder.from(0);
        builder.size(dto.getPageSize());
        // 3.6 设置高亮查询条件  三要素: title字段高亮   前置标签    后置标签
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<font style='color:red'>");
        highlightBuilder.postTags("</font>");
        builder.highlighter(highlightBuilder);
        searchRequest.source(builder);
        // 4. 执行搜索请求
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 5. 封装返回结果
            SearchHits hits = searchResponse.getHits();
            System.out.println("一共搜索到记录数: " + hits.getTotalHits().value);
            //   5.1 将返回结果 封装成List<SearchArticleVo>
            List<SearchArticleVo> articleVoList = new ArrayList<>();
            SearchHit[] docHits = hits.getHits();
            for (SearchHit docHit : docHits) {
                // 得到文章json内容
                String articleJson = docHit.getSourceAsString();
                // title 当前 是没有被高亮处理
                SearchArticleVo searchArticleVo = JSON.parseObject(articleJson, SearchArticleVo.class);
                //   5.2 解析高亮处理结果，将高亮结果设置到 vo的title字段中
                // key: 高亮字段    value: 高亮结果(片段数组)
                Map<String, HighlightField> highlightFields = docHit.getHighlightFields();
                // 获取title字段高亮处理结果
                HighlightField highlightResult = highlightFields.get("title");
                // 将高亮结果片段数组拼接成一个完整的字符串  就是title的高亮结果
                String title = StringUtils.join(highlightResult.getFragments());
                searchArticleVo.setTitle(title);
                articleVoList.add(searchArticleVo);
            }
            ResponseResult responseResult = ResponseResult.okResult(articleVoList);
            responseResult.setHost(webSite);
            return responseResult;
        } catch (IOException e) {
            e.printStackTrace();
            log.error("搜索索引库出现异常 ==> {}",e.getMessage());
            return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);
        }
    }

    @Override
    public void addArticle(SearchArticleVo article) {
        try {
            IndexRequest indexRequest = new IndexRequest(ArticleIndexConstants.ARTICLE_INDEX_NAME);
            indexRequest.id(article.getId()+"");
            indexRequest.source(JSON.toJSONString(article), XContentType.JSON);
            restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("添加索引库文档失败 ==> 原因: {}  , 文档:{}",e.getCause(),article);
        }
    }
    @Override
    public void deleteArticle(Long articleId) {
        DeleteRequest request = new DeleteRequest(ArticleIndexConstants.ARTICLE_INDEX_NAME,articleId+"");
        try {
        restHighLevelClient.delete(request,RequestOptions.DEFAULT);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("删除索引库文档失败 ==> 原因: {}  , 文档:{}",e.getCause(),articleId);
        }
    }
}
